<?php

declare(strict_types=1);
/**
 * This file is part of Hyperf.
 *
 * @link     https://www.hyperf.io
 * @document https://hyperf.wiki
 * @contact  group@hyperf.io
 * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
 */

namespace App\Common\Repositories\System;

use App\Common\Dao\System\CustomerAdminDao;
use App\Common\Repositories\BaseRepository;
use App\Exception\AppException;
use Hyperf\Cache\CacheManager;
use Hyperf\Coroutine\Coroutine;
use Hyperf\DbConnection\Db;
use Hyperf\Di\Annotation\Inject;
use Hyperf\HttpServer\Contract\RequestInterface;
use Hyperf\HttpServer\Router\Dispatched;
use xsl\enums\Code;
use xsl\enums\Status;
use xsl\services\JwtTokenService;

class CustomerAdminRepository extends BaseRepository
{
    protected bool $checkPassword = true;

    #[Inject]
    private CacheManager $cache;

    private int $token_exp = 24; // 小时

    private int $token_valid_exp = 15; // 分钟

    public function __construct(CustomerAdminDao $dao)
    {
        $this->dao = $dao;
    }

    public function getWhereList(array $fields = ['*'], array $where = [],bool $isPaginate = true, int $page = 1, int $limit = 10){
        $query = $this->dao->search($where)->select($fields);
        if($isPaginate){
            return $query->paginate($limit);
        }
        return $query->get();
    }

    public function toggleBy($id, string $field = 'status'){
        $model = $this->dao->get($id);
        $status = $model->$field == Status::ENABLE ? Status::DISABLE : Status::ENABLE;
        Db::beginTransaction();
        try{
            $update = [$field => $status];
            $this->dao->update(intval($id), $update);
            Db::commit();
        } catch(\Throwable $ex){
            Db::rollBack();
            throw new AppException($ex->getMessage(), Code::ERROR);
        }
        return true;
    }

    /**
     * @param mixed $params
     * @param mixed $request
     */
    public function attempt($params, $request): string
    {
        $where['account'] = $params['account'];
        $admin = $this->dao->getWhere($where);
        if (! $admin) {
            throw new AppException('登录失败', Code::LOGIN_FAILED);
        }
        if ($admin->status == Status::DISABLE) {
            throw new AppException('该用户已被禁用', Code::USER_FORBIDDEN);
        }
        if ($this->checkPassword && ! password_verify($params['password'], $admin->password)) {
            throw new AppException('登录失败, 请检查密码', Code::FAILED);
        }
        $domain = $this->getIp($request);
        $token = $this->createToken($admin, $domain);
        $browser = $this->getBrowserFrom($request->header('user-agent'));
        $platform = $this->getPlatformFrom($request->header('user-agent'));

        go(function () use ($admin, $token, $domain, $browser, $platform) {
            Coroutine::sleep(20);
            $this->log($admin->account, $domain, $browser, $platform);
            $admin->login_ip = $domain;
            $admin->login_at = time();
            $admin->remember_token = $token;
            $admin->save();
        });

        return $token;
    }

    public function createToken($adminInfo, $domain): string
    {
        $service = new JwtTokenService();
        $exp = $this->token_exp;
        $token = $service->createToken($adminInfo->id, 'admin', strtotime("+ {$exp}hour"), [], $domain);
        return $this->cacheToken($token['token'], $token['out']);
    }

    public function cacheToken(string $token, int $exp): string
    {
        $this->cache->getDriver()->set('admin_' . $token, time() + $exp, $exp);
        return $token;
    }

    public function checkToken(string $token): void
    {
        $has = $this->cache->getDriver()->has('admin_' . $token);
        if (! $has) {
            throw new AppException('无效的token', Code::LOGIN_EXPIRED);
        }
        $lastTime = $this->cache->getDriver()->get('admin_' . $token);
        if (($lastTime + ($this->token_valid_exp * 60)) < time()) {
            throw new AppException('token 已过期', Code::LOGIN_EXPIRED);
        }
    }

    public function isSuperAdmin(int $id): bool
    {
        return $id == 1;
        return $this->dao->existsWhere(['id' => $id]);
    }

    public function permissions(int $adminId)
    {
        $info = $this->dao->get($adminId);
        $customerPermissionsRepository = \Hyperf\Support\make(CustomerPermissionsRepository::class);
        // 获取超级管理配置 超级管理员全部权限
        $identifys = [];
        if ($this->isSuperAdmin($adminId)) {
            $permissionIds = $customerPermissionsRepository->getColumn(['hidden' => 1], 'id');
        } else {
            $roles = $info->roles;
            $permissionIds = [];
            foreach ($roles as $role) {
                $identifys[] = $role->identify;
                $permissions = $role->permissions;
                foreach ($permissions as $permission) {
                    $permissionIds[] = $permission->id;
                }
            }
        }

        $permissions = $customerPermissionsRepository->getMyModel()
            ->whereIn('id', array_unique($permissionIds))
            ->orderBy("sort","desc")
            ->where('hidden', 1)
            ->get()->each(function($item){
                $item->hidden = false;
                return $item;
            });
        $info->permissions = $permissions;
        unset($info->roles);
        $info->roles = $identifys;
        return $info;
    }

    public function getRoles(int $adminId): array
    {
        $result = $this->dao->get($adminId);
        $roles = $result->roles;
        $permissionIds = [];
        foreach ($roles as $role) {
            $permissions = $role->permissions;
            foreach ($permissions as $permission) {
                $permissionIds[] = $permission->id;
            }
        }
        return $permissionIds;
    }

    public function can(int $adminId, RequestInterface $request): bool
    {
        if ($this->isSuperAdmin($adminId)) {
            return true;
        }
        $controllerMethod = explode('@', $request->getAttribute(Dispatched::class)->handler->callback);
        // array(2) {
        //  [0]=>
        //  string(43) "\App\Controller\IndexController"
        //  [1]=>
        //  string(3) "index"
        // }
        $controller = $controllerMethod[0];
        $action = $controllerMethod[1];

        $controller = str_replace('App\Controller\api\\', '', $controller);
        // permissions\Admin
        $module = null;
        // 如果没有 \
        if (str_contains($controller, '\\')) {
            [$module, $controller] = explode('\\', $controller);
        }
        // 个人信息，
        if ($controller . '@' . $action == 'Admin@online' || $controller . '@' . $action == 'Options@get'
            || $controller . '@' . $action == 'Admin@loginLog' || $controller . '@' . $action == 'Admin@operateLog'
            || $controller . '@' . $action == 'Generate@excel'|| $controller . '@' . $action == 'Upload@image'
        ) {
            return true;
        }
        $permissionIds = $this->getRoles($adminId);

        $customerPermissionsRepository = \Hyperf\Support\make(CustomerPermissionsRepository::class);
        $id = $customerPermissionsRepository->getMyModel()->where('permission_mark', $controller . '@' . $action)
            ->when($module, function ($query) use ($module) {
                return $query->where('module', $module);
            })->value('id');
        return in_array($id, $permissionIds);
    }

    protected function log($account, $login_ip, $browser, $platform)
    {
        $customerLogLoginRepository = \Hyperf\Support\make(CustomerLogLoginRepository::class);
        $customerLogLoginRepository->create([
            'account' => $account,
            'login_ip' => $login_ip,
            'browser' => $browser,
            'platform' => $platform,
            'login_at' => time(),
            'status' => Status::ENABLE,
        ]);
    }

    protected function getDomain(RequestInterface $request)
    {
        return $request->url();
    }

    protected function getIp(RequestInterface $request)
    {
        return $request->getServerParams()['remote_addr'];
    }

    /**
     * get platform.
     */
    protected function getBrowserFrom(string $userAgent): string
    {
        return match (true) {
            str_contains($userAgent, 'Edge') => 'Edge',
            str_contains($userAgent, 'MSIE') => 'IE',
            str_contains($userAgent, 'Firefox') => 'Firefox',
            str_contains($userAgent, 'Chrome') => 'Chrome',
            str_contains($userAgent, 'Opera') => 'Opera',
            str_contains($userAgent, 'Safari') => 'Safari',
            default => 'unknown'
        };
    }

    /**
     * get os name.
     */
    protected function getPlatformFrom(string $userAgent): string
    {
        return match (true) {
            str_contains($userAgent, 'win') => 'Window',
            str_contains($userAgent, 'mac') => 'Mac OS',
            str_contains($userAgent, 'linux') => 'Linux',
            str_contains($userAgent, 'iphone') => 'IPhone',
            str_contains($userAgent, 'android') => 'Android',
            default => 'unknown'
        };
    }

    public function clearToken(string $token)
    {
        $this->cache->getDriver()->delete('admin_' . $token);
    }
}
